OpenTSDB schema 设计

TSDB 的一些特点:

  • 一般使用 KV 存储模型,检索的要素都保存在 key 中;
  • TSDB 常用于存储监控数据,其中监控的指标称为“metric”,以及多个 k=v 格式的标签(一般称为 label or tag ),例如 service=service1, ip=10.10.2.58,以及时间戳

例如 Prometheus 的 Key 设计为 http_request_total{status=200,method="GET"}@timestamp 的格式

OpenTSDB schema 设计

Overview

OpenTSDB 是基于 HBase 设计的,架构设计如下:

../_images/OpenTSDB-Overview.png

  • 图中 TSD 即为(对应实际进程名是 TSDMain) opentsdb 组件。每个实例 TSD 都是独立的。没有 master,没有共享状态(shared state),因此实际生产部署可能会通过 nginx+Consul 运行多个 TSD 实例以实现负载均衡;
  • Server 通过 TSD RPC 向 TSD 上报数据,TSD 则通过 HBase RPC 与 HBase 进行通讯;

行键的设计

OpenTSDB 的 Rowkey 格式为: [salt]<metric_uid><timestamp><tagk1><tagv1>[...<tagkN><tagvN>]

  • SALT: 为了充分利用 HBase 分布式 Region Server 的能力,SALT 可以有效避免 HBase 的写入热点问题,开启 SALT 后,会对创建的 HBase Scanner 数量有影响,HBase Scanner 数量等于 SALT 的数量;
  • Metric:监控指标名,例如 “sys.cpu.user”
  • timestamp:小时级时间戳
  • TagK-TagV:由多个标签的 KV 对构成,例如 “ip=10.10.2.58 core=0”

所以一个 Rowkey 的逻辑值看起来如下: sys.cpu.user 1541946115 ip=10.10.2.58 core=0

OpenTSDB 为了减少 Rowkey 的空间占用,Metric、Tag 等都使用 UID(Unique ID)来代替,UID 默认占用3字节,UID 占用字节数可以通过参数调整,
OpenTSDB 为每种 Metric、TagK、TagV 都生成一个 UID,Metric、TagK、TagV 各自拥有自己的 UID 空间,换句话说所有 Metric 的 UID 不会出现重复,但 Metric 和 TagK 之间可能有重复的 UID;

我们可以通过分别修改 tsd.storage.uid.width.metrictsd.storage.uid.width.tagk 以及 tsd.storage.uid.width.tagv 参数来设置对应编码占用的字节数。

所以 Rowkey 实际占用空间如下所示:

../_images/OpenTSDB-Rowkey.png

列的设计

由于 Rowkey 中已经包含了小时级的时间戳,所以需要通过列名(Qualifier)存储相对秒数(或毫秒数)

(1)当 OpenTSDB 接收到一个新的 DataPoint 的时候,如果请求中的时间戳是秒,那么列名(Qualifier)将占用 2字节:

../_images/OpenTSDB-Column-1.png

  • 低3位表示 Value 的长度,Value 的长度 = (qualifier & 0x07) + 1
  • 中间1位表示 Value 的类型,如果值为1,表示 Value 的类型为 float;如果值为0,表示 Value 的类型为 long。
  • 高12位表示相对于 Rowkey 中小时级时间戳的秒数,最大为 3600

判断请求中的时间戳为秒或毫秒的方法是基于时间戳数值的大小,如果时间戳的值的超过无符号整数的最大值(即4个字节的长度),那么该时间戳是毫秒,否则为秒

(2)如果 OpenTSDB 收到的 DataPoint 时间戳是毫秒,那么列名(Qualifier)将占用 4字节:

../_images/OpenTSDB-Column-2.png

  • Value 长度:与秒级时间戳相同;
  • Value 类型:与秒级时间戳相同;
  • 用22位表示相对于 Rowkey 中小时级时间戳的毫秒数
  • 高4位是(标志位)固定全为1;

表的设计

由于每种 Metric、TagK、TagV 都被映射为一个 UID,所以 OpenTSDB 还需要一张额外的表存储 UID:

(1)表 tsdb:所有的时序数据都存于这张表,Rowkey = [SALT] <metric> <小时级时间戳> <TagK1> <TagV1> ...,列族只有一个 t,列名为数据的时间戳相对于 Rowkey 中小时级时间戳的秒数(or 毫秒数),一个 CF:Qualifier 例如 t:3600

../_images/OpenTSDB-Table-1.png

(2)表 tsdb-uid:opentsdb 会将每种 metric、tagk、tagv 都映射成 UID,映射是双向的,比如说既可以根据 tagk 找到对应的 UID,也可以根据 UID 直接找到相应的 tagk。而这些映射关系就记录在 tsdb-uid 表中。该表有两个 ColumnFamily,分别是 name 和 id,另外这两个 ColumnFamily 下都有三列,分别是 metric、tagk、tagv。如下图所示:

../_images/OpenTSDB-Table-2.png

Reference

百度自研 TSDB

  • 底层存储使用 HBase
  • RowKey = entity_id + metric_id + timebase
    • entity_id 是由 tagK 和 tagV 经过 hash 得到的一个固定长度的值,hash 后原始字符串的自然顺序被打乱,使得 RowKey 能够相对均匀地分布在不同 HRegion 中;
    • metric_id 为 metric 的字符串 hash 值,同样是固定长度;
    • timebase 为 Unix 时间戳按照 1 小时(3600 秒)取整得到的数值,固定 4 个字节的长度
  • Column: 列名使用相对 Rowkey 小时级时间戳的秒数;

参考: